#ifndef NULL
#define NULL 0
#endif

#include <iostream>
#include <sstream>

#include "app/ApplicationConfig.h"

#include "cli/ConsoleVisitor.h"

#include "core/Graph.h"
#include "core/path/DijkstraShortestPath.h"
#include "core/path/DistanceCost.h"
#include "core/path/TimeCost.h"

#include "map/MapFactory.h"
#include "parser/Rule.h"
#include "parser/Parser.h"
#include "parser/ParsedData.h"
#include "parser/exceptions/InvalidDataException.h"
#include "util/logger/Logger.h"
#include "util/logger/LoggerManager.h"
#include "util/GraphElementBuilder.h"
#include "util/exceptions/ElementNotFoundException.h"
#include "util/StringUtils.h"

#include "map/MapLocation.h"

Vertex* chooseVertex(Graph* graph);
Vertex* getVertexFromUser(Graph& graph, std::string name);
void consoleWritePath(Graph* aGraph, Vertex* origin, Vertex* destination,
		path::PathSolverAlgorithm &solver);

int main2(int argc, char **argv) {
	if (!app::ApplicationConfig::loadConfigFromFile("res/Configuracion.txt")) {
		printf("No se pudo cargar la configuracion de la aplicacion de res/Configuracion.txt.");
		return 1;
	}

	util::Logger * logger = util::LoggerManager::getInstanceConfigSafe()->getLogger();

	logger->info("Inicio del programa.\n");

	MapFactory factory;
	Graph* graph = factory.createMap("res")->getGraph();

	if (graph->isEmpty()) {
		logger->info("Ningun vertice fue cargado con exito. Revise los logs y corrija los archivos de entrada.");
		logger->info("Se procede a finalizar el programa ya que no hay grafo con el que trabajar.");
		logger->~Logger();
		return 1;
	}

	logger->info("Iniciando busqueda.");

	Vertex* orig = getVertexFromUser(*graph, "ORIGEN");
	Vertex* dest = getVertexFromUser(*graph, "DESTINO");
	path::DijkstraShortestPath fastPath(new path::TimeCost());
	path::DijkstraShortestPath shortestPath(new path::DistanceCost());

	int opt = -1;
	std::string input;
	while (opt != 0) {
		std::cout << "\n\nBuscar camino entre los vertices seleccionados:\n";
		std::cout << "  1) Camino mas corto\n";
		std::cout << "  2) Camino mas rapido\n";
		std::cout << "  3) Elegir otros vertices\n";
		std::cout << "  0) Salir\n";
		std::cout << "Opcion: ";
		std::getline(std::cin, input);
		std::stringstream(input) >> opt;
		switch (opt) {
		case 1:
			consoleWritePath(graph, orig, dest, shortestPath);
			break;
		case 2:
			consoleWritePath(graph, orig, dest, fastPath);
			break;
		case 3:
			orig = getVertexFromUser(*graph, "ORIGEN");
			dest = getVertexFromUser(*graph, "DESTINO");
			break;
		}
	}
	logger->info("Fin del programa.\n");
	logger->~Logger();
	return 0;
}

// simplemente pide al usuario que elija un vertice de los que hay en el grafo
Vertex* chooseVertex(Graph* graph) {
	ConsoleVisitor visitor;
	graph->visitVertices(visitor);
	std::cout << "  Id Vertice? ";
	std::string input;
	std::getline(std::cin, input);
	try {
		return graph->getVertex(input);
	} catch (util::ElementNotFoundException e) {
		std::cout << "ERROR: Vertice invalido!\n\n";
		return chooseVertex(graph);
	}
}

// imprime el camino entre dos vertices si existe.
void consoleWritePath(Graph* aGraph, Vertex* origin, Vertex* destination,
		path::PathSolverAlgorithm &solver) {
	util::Logger* logger = util::LoggerManager::getInstance()->getLogger();
	try {
		double total = 0.0;
		logger->info("Salgo de " + origin->getId());
		std::cout << "Buscando camino: " + origin->getId() + " --> "
				+ destination->getId() + " ... ";
		//std::cout << "origen ---arista--> destino (costo actual|costo acumulado)\n";
		path::Path* solution = aGraph->solvePath(solver, origin, destination);
		std::cout << "camino encontrado!\n\n";
		path::Iterator it = solution->iterator();
		std::cout << "Se parte de " + origin->getId() + "\n";
		while (it.hasNext()) {
			it.next();
			double currentCost = solver.cost(it.transversingEdge());
			total += currentCost;

			std::cout << it.currentVertex()->getId() + " ----";
			std::cout << it.transversingEdge()->getId() + "---> ";
			std::cout << it.nextVertex()->getId() + "(";
			std::cout << util::doubleToString(currentCost) + "|";
			std::cout << util::doubleToString(total) + ")\n";

			logger->info(it.currentVertex()->getId() + " ("
					+ it.transversingEdge()->getId() + ") "
					+ it.nextVertex()->getId() + " | costo: "
					+ util::doubleToString(currentCost) + " | acumulado: "
					+ util::doubleToString(total));
		}
		std::cout << "Se llega a  " + destination->getId() + "\n";
		logger->info("Se llega al vertice: " + destination->getId() + "\n");
	} catch (OutOfReachException e) {
		std::cout << "No existe camino posible.";
		logger->info("No existe camino posible entre " + origin->getId()
				+ " y " + destination->getId());
	}
}

Vertex* getVertexFromUser(Graph& graph, std::string name) {
	util::Logger* logger = util::LoggerManager::getInstance()->getLogger();
	std::cout << " Elija el vertice de " << name << ":\n";
	Vertex* ret = chooseVertex(&graph);
	logger->info(ret->getId());
	return ret;
}
